ipdata = {
    prefStart = {},
    prefEnd = {},
    data = {},
    v6_num = 0,
    filename = "",
    fsize = 0,
    mode = "",
    endArr = {},
    addrArr = {},
    searchType = "",
    file = {},
    _version = "0.1.2",
    big_num = { '170141183460469231731687303715884105728', '85070591730234615865843651857942052864', '42535295865117307932921825928971026432', '21267647932558653966460912964485513216', '10633823966279326983230456482242756608', '5316911983139663491615228241121378304', '2658455991569831745807614120560689152', '1329227995784915872903807060280344576', '664613997892457936451903530140172288', '332306998946228968225951765070086144', '166153499473114484112975882535043072', '83076749736557242056487941267521536', '41538374868278621028243970633760768', '20769187434139310514121985316880384', '10384593717069655257060992658440192', '5192296858534827628530496329220096', '2596148429267413814265248164610048', '1298074214633706907132624082305024', '649037107316853453566312041152512', '324518553658426726783156020576256', '162259276829213363391578010288128', '81129638414606681695789005144064', '40564819207303340847894502572032', '20282409603651670423947251286016', '10141204801825835211973625643008', '5070602400912917605986812821504', '2535301200456458802993406410752', '1267650600228229401496703205376', '633825300114114700748351602688', '316912650057057350374175801344', '158456325028528675187087900672', '79228162514264337593543950336', '39614081257132168796771975168', '19807040628566084398385987584', '9903520314283042199192993792', '4951760157141521099596496896', '2475880078570760549798248448', '1237940039285380274899124224', '618970019642690137449562112', '309485009821345068724781056', '154742504910672534362390528', '77371252455336267181195264', '38685626227668133590597632', '19342813113834066795298816', '9671406556917033397649408', '4835703278458516698824704', '2417851639229258349412352', '1208925819614629174706176', '604462909807314587353088', '302231454903657293676544', '151115727451828646838272', '75557863725914323419136', '37778931862957161709568', '18889465931478580854784', '9444732965739290427392', '4722366482869645213696', '2361183241434822606848', '1180591620717411303424', '590295810358705651712', '295147905179352825856', '147573952589676412928', '73786976294838206464', '36893488147419103232', '18446744073709551616', '9223372036854775808', '4611686018427387904', '2305843009213693952', '1152921504606846976', '576460752303423488', '288230376151711744', '144115188075855872', '72057594037927936', '36028797018963968', '18014398509481984', '9007199254740992', '4503599627370496', '2251799813685248', '1125899906842624', '562949953421312', '281474976710656', '140737488355328', '70368744177664', '35184372088832', '17592186044416', '8796093022208', '4398046511104', '2199023255552', '1099511627776', '549755813888', '274877906944', '137438953472', '68719476736', '34359738368', '17179869184', '8589934592', '4294967296', '2147483648', '1073741824', '536870912', '268435456', '134217728', '67108864', '33554432', '16777216', '8388608', '4194304', '2097152', '1048576', '524288', '262144', '131072', '65536', '32768', '16384', '8192', '4096', '2048', '1024', '512', '256', '128', '64', '32', '16', '8', '4', '2', '1' },
}

function compareLargeNumberStrings(str1, str2)
    -- 先比较长度
    if #str1 > #str2 then
        return 1
    elseif #str1 < #str2 then
        return -1
    else
        -- 长度相同，逐位比较
        for i = 1, #str1 do
            local digit1 = str1:sub(i, i)
            local digit2 = str2:sub(i, i)

            if digit1 > digit2 then
                return 1
            elseif digit1 < digit2 then
                return -1
            end
        end
        return 0
    end
end


function unpackInt4byte(a, b, c, d)
    return (a % 256) + (b * 256) + (c * 65536) + (d * 16777216)
end

function fsize(file)
    local current = file:seek()
    local size = file:seek("end")
    file:seek("set", current)
    return size
end

function redaFileBytes(file, length, off)
    file:seek("set", off)
    local str = file:read(length)
    local bytes = {}
    for i = 1, #str do
        table.insert(bytes, string.byte(str, i))
    end
    return bytes
end


function ipdata.LoadFileV6(path, mode)
    ipdata.mode = mode
    ipdata.searchType = "ipv6"
    ipdata.file = io.open(path, "rb")
    ipdata.fsize = fsize(ipdata.file)
    ipdata.filename = path
    local num_data = redaFileBytes(ipdata.file, 4, 4)
    ipdata.v6_num = unpackInt4byte(num_data[1], num_data[2], num_data[3], num_data[4])
    if mode == "file" then
        for k = 1, ipdata.v6_num do
            local i = (k - 1) * 12 + 8
            local sizeData = redaFileBytes(ipdata.file, 12, i)
            local preoff = unpackInt4byte(sizeData[9], sizeData[10], sizeData[11], sizeData[12])
            ipdata.prefStart[preoff] = unpackInt4byte(sizeData[1], sizeData[2], sizeData[3], sizeData[4])
            ipdata.prefEnd[preoff] = unpackInt4byte(sizeData[5], sizeData[6], sizeData[7], sizeData[8])
        end
    else
        local sizeData = redaFileBytes(ipdata.file, ipdata.v6_num * 12, 8)
        for k = 1, ipdata.v6_num do
            local i = (k - 1) * 12
            local preoff = unpackInt4byte(sizeData[i + 9], sizeData[i + 10], sizeData[i + 11], sizeData[i + 12])
            ipdata.prefStart[preoff] = unpackInt4byte(sizeData[i + 1], sizeData[i + 2], sizeData[i + 3], sizeData[i + 4])
            ipdata.prefEnd[preoff] = unpackInt4byte(sizeData[i + 5], sizeData[i + 6], sizeData[i + 7], sizeData[i + 8])
        end

        sizeData = redaFileBytes(ipdata.file, 4, 0)
        local recordSize = unpackInt4byte(sizeData[1], sizeData[2], sizeData[3], sizeData[4])
        local p = ipdata.v6_num * 12 + 4 + 4
        ipdata.file:seek("set", p)
        local content_data = ipdata.file:read("*a")

        for i = 1, recordSize do
            local off = (i - 1) * 55 + 1
            local data_len = (i - 1) * 55 + 50
            ipdata.endArr[i] = string.sub(content_data, off, data_len)

            local bytes = {}
            local aaa = string.sub(content_data, data_len + 1, data_len + 5)
            for i = 1, #aaa do
                table.insert(bytes, string.byte(aaa, i))
            end

            local offset = unpackInt4byte(bytes[1], bytes[2], bytes[3], bytes[4]) - (ipdata.v6_num * 12 + 4 + 4)
            local length = bytes[5] % 256

            local tempData = string.sub(content_data, offset + 1, offset + length)
            ipdata.addrArr[i - 1] = tempData
        end
    end
end

function ipdata.LoadFileV4(path, mode)
    ipdata.mode = mode
    ipdata.searchType = "ipv4"
    ipdata.file = io.open(path, "rb")
    ipdata.fsize = fsize(ipdata.file)
    ipdata.filename = path
    if mode == "file" then
        for k = 1, 256 do
            local i = k * 8 + 4
            local sizeData = redaFileBytes(ipdata.file, 8, i)
            ipdata.prefStart[k] = unpackInt4byte(sizeData[1], sizeData[2], sizeData[3], sizeData[4])
            ipdata.prefEnd[k] = unpackInt4byte(sizeData[5], sizeData[6], sizeData[7], sizeData[8])
        end
    else
        ipdata.data = ipdata.file:read("*a")
        for k = 1, 256 do
            local i = k * 8 + 4
            ipdata.prefStart[k] = unpackInt4byte(string.byte(ipdata.data, i+1),string.byte(ipdata.data, i+2), string.byte(ipdata.data, i+3),
            string.byte(ipdata.data, i+4))
            ipdata.prefEnd[k] = unpackInt4byte(string.byte(ipdata.data, i+5), string.byte(ipdata.data, i+6), string.byte(ipdata.data, i+7),
            string.byte(ipdata.data, i+8))
        end
        ipdata.file:close()
    end
end

function ipdata.reload()
    if ipdata.mode == "file" then 
        print("文件版查询不支持重载")
        return
    end
    ipdata.file = io.open(ipdata.filename, "rb")
    ipdata.fsize = fsize(ipdata.file)
    local dataR = ipdata.file:read("*a")
    local prefStartR = {}
    local prefEndR = {}
    if ipdata.searchType == "ipv4" then
        for k = 1, 256 do
            local i = k * 8 + 4
            prefStartR[k] = unpackInt4byte(string.byte(dataR, i +1), string.byte(dataR, i +2), string.byte(dataR, i +3),string.byte(dataR, i +4))
            prefEndR[k] = unpackInt4byte(string.byte(dataR, i +5), string.byte(dataR, i +6), string.byte(dataR, i +7),string.byte(dataR, i +8))
        end
        ipdata.data = dataR
        ipdata.prefStart = prefStartR
        ipdata.prefEnd = prefEndR
        dataR= {};
        prefStartR = {}
        prefEndR = {}
    else
        local num_data = redaFileBytes(ipdata.file, 4, 4)
        local v6_numR = unpackInt4byte(num_data[1], num_data[2], num_data[3], num_data[4])
        local sizeData = redaFileBytes(ipdata.file, ipdata.v6_num * 12, 8)
        local prefStartR = {}
        local prefEndR = {}
        local endArrR = {}
        local addrArrR = {}
        for k = 1, v6_numR do
            local i = (k - 1) * 12
            local preoff = unpackInt4byte(sizeData[i + 9], sizeData[i + 10], sizeData[i + 11], sizeData[i + 12])
            prefStartR[preoff] = unpackInt4byte(sizeData[i + 1], sizeData[i + 2], sizeData[i + 3], sizeData[i + 4])
            prefEndR[preoff] = unpackInt4byte(sizeData[i + 5], sizeData[i + 6], sizeData[i + 7], sizeData[i + 8])
        end

        local recordSize = unpackInt4byte(sizeData[1], sizeData[2], sizeData[3], sizeData[4])
        local p = v6_numR * 12 + 4 + 4
        ipdata.file:seek("set", p)
        local content_data = ipdata.file:read("*a")

        for i = 1, recordSize do
            local off = (i - 1) * 55 + 1
            local data_len = (i - 1) * 55 + 50
            endArrR[i] = string.sub(content_data, off, data_len)

            local bytes = {}
            local aaa = string.sub(content_data, data_len + 1, data_len + 5)
            for i = 1, #aaa do
                table.insert(bytes, string.byte(aaa, i))
            end

            local offset = unpackInt4byte(bytes[1], bytes[2], bytes[3], bytes[4]) - (ipdata.v6_num * 12 + 4 + 4)
            local length = bytes[5] % 256

            local tempData = string.sub(content_data, offset + 1, offset + length)
            addrArrR[i - 1] = tempData
        end
        ipdata.file:close()
        ipdata.data = ipdata.dataR
        ipdata.v6_num = v6_numR
        ipdata.prefStart = prefStartR
        ipdata.prefEnd = prefEndR
        ipdata.endArr = endArrR
        ipdata.addrArr = addrArrR
        dataR= {}
        prefStartR = {}
        prefEndR = {}
        endArrR = {}
        addrArrR = {}
    end
end

function ipdata.getAddr(row)
    local j= 0
    local offset = 0
    local length = 0
    local tempData = ""
    if ipdata.searchType == "ipv4" then
        j = 2052 + (row * 9)
        if ipdata.mode == "file" then
            ipdata.file:seek("set", j + 4)
            local tempIndexData = ipdata.file:read(5)
            offset = unpackInt4byte(string.byte(tempIndexData, 1), string.byte(tempIndexData, 2),string.byte(tempIndexData, 3), string.byte(tempIndexData, 4))
            length = string.byte(tempIndexData, 5)
            ipdata.file:seek("set", offset)
            tempData = ipdata.file:read(length)
        else
            offset = unpackInt4byte(string.byte(ipdata.data,j+5), string.byte(ipdata.data,j+6),string.byte(ipdata.data,j+7),string.byte(ipdata.data,j+8))
            length = string.byte(ipdata.data, j+9)
            tempData = string.sub(ipdata.data,offset + 1,offset +length)
        end
        return tempData
        
    else
        j = ipdata.v6_num * 12 + 4 + 4 + (row * 55)
        if ipdata.mode == "file" then
            ipdata.file:seek("set", j + 50)
            local tempIndexData = ipdata.file:read(5)
            offset = unpackInt4byte(string.byte(tempIndexData, 1), string.byte(tempIndexData, 2),string.byte(tempIndexData, 3), string.byte(tempIndexData, 4))
            length = string.byte(tempIndexData, 5)
            ipdata.file:seek("set", offset)
            tempData = ipdata.file:read(length)
            return tempData
        else
            return ipdata.addrArr[row]
        end
    end
end

function ip2int(ip)
    local o1, o2, o3, o4 = string.match(ip, "(%d+)%.(%d+)%.(%d+)%.(%d+)")
    local num = 2 ^ 24 * o1 + 2 ^ 16 * o2 + 2 ^ 8 * o3 + o4
    return math.floor(num)
end

function ipdata.Search(low, high, k)
    local M = 0
    while low <= high do
        local endipNum = ""
        local mid = math.floor((low + high) / 2)
        if ipdata.searchType == "ipv4" then
            local j = 2052 + (mid * 9)
            if ipdata.mode == "file" then
                local allsizeData = redaFileBytes(ipdata.file, 4, j)
                endipNum = unpackInt4byte(allsizeData[1], allsizeData[2], allsizeData[3], allsizeData[4])
            else
                endipNum = unpackInt4byte(string.byte(ipdata.data,j+1),string.byte(ipdata.data,j+2),string.byte(ipdata.data,j+3),string.byte(ipdata.data,j+4))
            end
            if endipNum == nil then
                break
            end
            if endipNum >= (k) then
                M = mid
                if mid == 0 then
                    break
                end
                high = mid - 1
            else
                low = mid + 1
            end
        else
            if ipdata.searchType == "file" then
                local j = ipdata.v6_num * 12 + 4 + 4 + (mid * 55)
                ipdata.file:seek("set", j)
                endipNum = string.gsub(ipdata.file:read(50), "*", "")
            else
                endipNum = string.gsub(ipdata.endArr[mid + 1], "*", "")
            end
            if endipNum == nil then
                break
            end
            if compareLargeNumberStrings(endipNum,k) >=0 then
                M = mid
                if mid == 0 then
                    break
                end
                high = mid - 1
            else
                low = mid + 1
            end
        end

        
    end
    return M
end

function ipdata.FindIP(ip)
    local low = 0
    local high = 0
    local intIP = "0"
    if ipdata.searchType == "ipv4" then
        local ips = { string.match(ip, "([^.]+).?") }
        local prefix = tonumber(ips[1])
        if prefix == 0 then
            prefix = 1
        end
        low = ipdata.prefStart[prefix]
        high = ipdata.prefEnd[prefix]
        intIP = ip2int(ip)
    else
        local ip_str = tranSimIpv6ToFullIpv6(ip)
        local prefix = tonumber(string.sub(ip_str, 1, 4), 16)
        low = ipdata.prefStart[prefix]
        high = ipdata.prefEnd[prefix]
        local ip_2 = ipv6bin(ip_str)
        for i = 1, string.len(ip_2) do
            if string.sub(ip_2, i, i) == '1' then
                local num1 = ipdata.big_num[i]
                if string.len(num1) > string.len(intIP) then
                    intIP = bigNumberAdd(num1, intIP)
                else
                    intIP = bigNumberAdd(intIP, num1)
                end
            end
        end
    end

    local cur = 0
    if low == high then
        cur = low
    else
        cur = ipdata.Search(low, high, intIP)
    end

    if cur == 100000000 then
        return "0.0.0.0"
    else
        return ipdata.getAddr(cur)
    end
end

function bigNumberAdd(a, b)
    local result = ""
    local carry = 0
    local lenA = #a
    local lenB = #b
    local i = 1

    if lenA < lenB then
        a, b = b, a
        lenA, lenB = lenB, lenA
    end

    while i <= lenB do
        local sum = tonumber(a:sub(lenA - i + 1, lenA - i + 1)) + tonumber(b:sub(lenB - i + 1, lenB - i + 1)) + carry
        result = tostring(sum % 10) .. result
        carry = math.floor(sum / 10)
        i = i + 1
    end

    while i <= lenA do
        local sum = tonumber(a:sub(lenA - i + 1, lenA - i + 1)) + carry
        result = tostring(sum % 10) .. result
        carry = math.floor(sum / 10)
        i = i + 1
    end

    if carry > 0 then
        result = tostring(carry) .. result
    end

    return result
end

function ipv6bin(ipstr)
    ipstr = tranSimIpv6ToFullIpv6(ipstr)
    ipstr = ipstr:gsub(":", "")
    local ipbin = ""
    for i = 1, 32, 2 do
        local str_16 = tonumber(string.sub(ipstr, i, i + 1), 16)
        local bin = ""
        while str_16 > 0 do
            bin = tostring(str_16 % 2) .. bin
            str_16 = math.floor(str_16 / 2)
        end
        bin = string.sub("00000000" .. bin, -8)
        ipbin = ipbin .. bin
    end
    return ipbin
end

function tranSimIpv6ToFullIpv6(simpeIpv6)
    simpeIpv6 = string.upper(simpeIpv6)

    if simpeIpv6 == "::" then
        return "0000:0000:0000:0000:0000:0000:0000:0000"
    end

    local arr = { "0000", "0000", "0000", "0000", "0000", "0000", "0000", "0000" }
    if string.sub(simpeIpv6, 1, 2) == "::" then
        local tmpArr = {}
        for part in string.gmatch(string.sub(simpeIpv6, 3), "%S+") do
            table.insert(tmpArr, string.sub("0000" .. part, -4))
        end
        for i = 1, #tmpArr do
            arr[i + 8 - #tmpArr] = tmpArr[i]
        end
    elseif string.sub(simpeIpv6, -2) == "::" then
        local tmpArr = {}
        for part in string.gmatch(string.sub(simpeIpv6, 1, -3), "%S+") do
            local aaa = string_split(part, ":")
            for j = 1, #aaa do
                table.insert(tmpArr, string.sub("0000" .. aaa[j], -4))
            end
        end
        for i = 1, #tmpArr do
            arr[i] = tmpArr[i]
        end
    elseif string.find(simpeIpv6, "::") then
        local tmpArr = string_split(simpeIpv6, "::")
        local tmpArr0 = string_split(tmpArr[1], ":")
        for i = 1, #tmpArr0 do
            arr[i] = string.sub("0000" .. tmpArr0[i], -4)
        end
        local tmpArr1 = string_split(tmpArr[2], ":")
        for i = 1, #tmpArr1 do
            arr[i + 8 - #tmpArr1] = string.sub("0000" .. tmpArr1[i], -4)
        end
    else
        local tmpArr = string_split(simpeIpv6, ":")
        for i = 1, #tmpArr do
            arr[i + 8 - #tmpArr] = string.sub("0000" .. tmpArr[i], -4)
        end
    end
    return table.concat(arr, ":")
end

function string_split(str, delimiter)
    local result = {}
    for match in (str .. delimiter):gmatch("(.-)" .. delimiter) do
        table.insert(result, match)
    end
    return result
end

return ipdata
